package com.budwk.app.sqfw.component;

import com.alibaba.fastjson.JSONObject;
import com.budwk.app.ocr_general.HttpUtil;
import com.budwk.app.sqfw.constants.CommonConstants;
import com.budwk.starter.common.result.Result;
import com.budwk.starter.common.result.ResultCode;
import com.oppo.push.server.Environment;
import com.oppo.push.server.Notification;
import com.oppo.push.server.Sender;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * oppo服务类
 */
@Slf4j
@IocBean
public class OppoComponent {

    @Inject
    private PropertiesProxy propertiesProxy;

    private static final String PUSH_APPKEY = "push.msg.oppo.appKey";

    private static final String PUSH_MASTER_SECRET = "push.msg.oppo.masterSecret";

    private static final String OPPO_VERSION_CLIENTID = "oppo.getOnlineVersion.clientId";

    private static final String OPPO_VERSION_CLIENT_SECRET = "oppo.getOnlineVersion.clientSecret";

    private static final String OPPO_VERSION_GRANTTYPE = "oppo.getOnlineVersion.grantType";

    private static final String OPPO_VERSION_URL = "oppo.getOnlineVersion.url";

    // 获取token后缀url
    private static final String OPPO_GET_ACCESS_TOKEN_URL = "/oauth2/v1/token";

    // 查询版本应用号的url后缀
    private static final String OPPO_QUERY_VERSION = "/selfupdate/query/version";

    /**
     * 获取发送Sender
     */
    public Sender pushSender() throws Exception {
        String appKey = propertiesProxy.get(PUSH_APPKEY);
        String masterSecret = propertiesProxy.get(PUSH_MASTER_SECRET);
        Sender sender = Sender.newBuilder()
                .appKey(appKey) // 设置appKey
                .masterSecret(masterSecret) // 设置masterSecret
                .env(Environment.CHINA_PRODUCTION)  // 中国
                .httpMaxConnection(64) // 设置http最大连接数
                .httpMaxRoute(64)   // 设置最大http路由连接数
                .httpConnectionTimeout(5000) // http连接超时时间
                .httpConnectRequestTimeout(5000) // 等待连接超时时间
                .httpSocketTimeout(5000) // socket超时时间
                .build();
        return sender;
    }

    /**
     * 这是构造一个通知栏消息体Notification对象的实例方法。
     * 请注意，后续的例子会调用这个getNotification()方法
     */
    public Notification getNotification(String msgContent, String action, String sqfwId) {
        Notification notification = new Notification();
        // 标题，内容是必填项
        notification.setTitle("祥云小助");
        notification.setContent(msgContent);
        //通知栏样式 1. 标准样式  2. 长文本样式  3. 大图样式 【非必填，默认1-标准样式】
        notification.setStyle(1);
        // App开发者自定义消息Id，OPPO推送平台根据此ID做去重处理，对于广播推送相同appMessageId只会保存一次，对于单推相同appMessageId只会推送一次
        notification.setAppMessageId(UUID.randomUUID().toString());
        // App开发者自定义回执参数，字数限制50以内，中英文均以一个计算
        notification.setCallBackParameter("");
        // 点击动作类型0，启动应用；1，打开应用内页（activity的intent action）；2，打开网页；4，打开应用内页（activity）；【非必填，默认值为0】;5,Intent scheme URL
        notification.setClickActionType(0);
        notification.setClickActionActivity("com.dfsjsoft.communityassistant.ui.main.MainActivity");
        JSONObject parametersJson = new JSONObject();
        parametersJson.put("action", action);
        parametersJson.put(action, sqfwId);
        notification.setActionParameters(JSONObject.toJSONString(parametersJson));
        // 展示类型 (0, “即时”),(1, “定时”)
        notification.setShowTimeType(0);
        // 定时展示开始时间（根据time_zone转换成当地时间），时间的毫秒数
        notification.setShowStartTime(System.currentTimeMillis() + 1000 * 60 * 3);
        // 定时展示结束时间（根据time_zone转换成当地时间），时间的毫秒数
        notification.setShowEndTime(System.currentTimeMillis() + 1000 * 60 * 5);
        // 是否进离线消息,【非必填，默认为True】
        notification.setOffLine(true);
        // 离线消息的存活时间(time_to_live) (单位：秒), 【off_line值为true时，必填，最长3天】
        notification.setOffLineTtl(24 * 3600);
        // 时区，默认值：（GMT+08:00）北京，香港，新加坡
        notification.setTimeZone("GMT+08:00");
        // 0：不限联网方式, 1：仅wifi推送
        notification.setNetworkType(0);
        notification.setChannelId("com.dfsjsoft.communityassistant.CHANNEL_TASK_MSG");
        return notification;
    }

    /**
     * 查询oppo应用版本号
     * @param accessToken 授权token
     *
     * @return 结果集
     * @throws IOException 业务异常
     */
    public Result queryOppoApplyLatestVersion(String accessToken) throws IOException {
        // 1 获取请求url
        String url = propertiesProxy.get(OPPO_VERSION_URL) + OPPO_QUERY_VERSION;
        // 2 组装参数
        String timeMillis = String.valueOf(System.currentTimeMillis());
        int ran = new Random().nextInt(20000);
        // 3 组装查询请求参数
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("pkg", "com.dfsjsoft.communityassistant");
        // 4 签名
        Optional<String> optional = sign(accessToken, timeMillis, String.valueOf(ran), propertiesProxy.get(OPPO_VERSION_CLIENT_SECRET), paramMap);
        // 5 设置请求头参数
        Map<String,String> header = new HashMap<>();
        header.put(HttpHeaders.AUTHORIZATION, accessToken);
        header.put("X-Client-Send-Utc-Ms", timeMillis);
        header.put("X-Nonce", String.valueOf(ran));
        header.put("X-Api-Sign", optional.orElseThrow());
        // 6 调用查询接口 获取结果
        JSONObject resJson = HttpUtil.sendGet(url, paramMap, header);
        log.info("oppo queryOppoApplyLatestVersion res: {}", resJson);
        // 7 解析结果
        int status = resJson.getIntValue("status");
        if (HttpStatus.SC_OK != status) {
            return Result.error(resJson.getString("message"));
        }
        JSONObject result = resJson.getJSONObject(CommonConstants.RESULT);
        return Result.success(result);
    }

    /**
     * 获取授权token
     * @return 授权token
     * @throws IOException 业务异常
     */
    public Optional<String> getAccessToken() throws IOException {
        String url = propertiesProxy.get(OPPO_VERSION_URL) + OPPO_GET_ACCESS_TOKEN_URL;
        Map<String, String> paramMap = new HashMap<>(3);
        paramMap.put("client_id", propertiesProxy.get(OPPO_VERSION_CLIENTID));
        paramMap.put("client_secret", propertiesProxy.get(OPPO_VERSION_CLIENT_SECRET));
        paramMap.put("grant_type", propertiesProxy.get(OPPO_VERSION_GRANTTYPE));
        JSONObject resJson = HttpUtil.sendGet(url, paramMap, null);
        // 解析返回参数
        log.info("oppo getAccessToken res: {}", resJson);
        String code = resJson.getString(CommonConstants.CODE);
        if (StringUtils.isEmpty(code)) {
            throw new IOException(ResultCode.NULL_DATA_ERROR.getMsg());
        }
        if (!"0".equals(code)) {
            throw new IOException(resJson.getString(CommonConstants.MESSAGE));
        }
        JSONObject dataJson = resJson.getJSONObject(CommonConstants.DATA);
        return Optional.of(dataJson.getString("access_token"));
    }


    /**
     * 对请求参数进行签名
     * @param secret
     * @param paramMap
     * @return String
     * @throws IOException
     */
    private static Optional<String> sign(String accessToken, String timestamp, String nonce, String secret, Map<String, String> paramMap){
        List<String> keysList = new ArrayList<>(paramMap.keySet());
        Collections.sort(keysList);
        List<String> paramList = new ArrayList<>();
        for (String key : keysList) {
            Object object = paramMap.get(key);
            if (object == null){
                continue;
            }
            String value = key + CommonConstants.EQUAL + object;
            paramList.add(value);
        }
        String str = "access_token="+accessToken+"&timestamp="+timestamp+"&nonce="+nonce;
        if(!paramList.isEmpty()){
            String signStr = String.join(CommonConstants.ALSO, paramList);
            str = str + CommonConstants.ALSO +signStr;
        }
        return hmacSHA256(str, secret);
    }

    /**
     * HMAC_SHA256 计算签名
     * @param data 需要加密的参数
     * @param key 签名密钥
     * @return String 返回加密后字符串
     */
    private static Optional<String> hmacSHA256(String data, String key) {
        try {
            byte[] secretByte = key.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec signingKey = new SecretKeySpec(secretByte, CommonConstants.HMACSHA256);
            Mac mac = Mac.getInstance(CommonConstants.HMACSHA256);
            mac.init(signingKey);
            byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
            byte[] by = mac.doFinal(dataByte);
            return Optional.of(byteArr2HexStr(by));
        } catch (Exception e) {
            log.error("hmacSHA256 error: ", e);
        }
        return Optional.empty();
    }

    /**
     * 字节数组转换为十六进制
     * @param bytes
     * @return String
     */
    private static String byteArr2HexStr(byte[] bytes) {
        int length= bytes.length;
        // 每个byte用两个字符才能表示，所以字符串的长度是数组长度的两倍
        StringBuilder sb = new StringBuilder(length * 2);
        for (int i = 0; i < length; i++) {
            // 将得到的字节转16进制
            String strHex = Integer.toHexString(bytes[i] & 0xFF);
            // 每个字节由两个字符表示，位数不够，高位补0
            sb.append((strHex.length() == 1) ? "0" + strHex : strHex);
        }
        return sb.toString();
    }
}
